
package carmadness.model;

import carmadness.GameState;
import carmadness.Game;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.LinkedList;
import java.util.Iterator;

/**
 * A világot reprezentáló osztály. Gondoskodik az út hálózat felépítéséről,
 * nyilvántartja annak elemeit, és közvetíti az órajelet ezen elemekhez.
 * 
 * @author Szelei
 * @version 1.0
 * @created 10-Apr-2010 2:32:44 PM
 */
public class World implements ISteppable
{
	/**
	 * A bankra mutató referencia
	 */
	private Bank bank;
	/**
	 * A játékosra mutató referencia.
	 */
	private Robber player;
	/**
	 * A rendőrre mutató referencia.
	 */
	private Police police;
	/**
	 * Rendőrörsre mutató referencia.
	 */
	private PoliceStation policeStation;
	/**
	 * Az összes útelemet tároló lista.
	 */
	private List<RoadSegment> roadSegments = new LinkedList<RoadSegment>();
	/**
	 * A világban található források listája.
	 */
	private List<SourceRoadSegment> sources = new LinkedList<SourceRoadSegment>();
	/**
	 * A léptethető elemeket tároló attribútum.
	 */
	private List<ISteppable> steppables = new LinkedList<ISteppable>();
	/**
	 * Egy step iterációban törölt elemek tárolója. (Konkurens hozzáférés
	 * kezelésére.)
	 */
	private List<ISteppable> steppablesToRemove = new LinkedList<ISteppable>();
	/**
	 * A világ kiterjedése vízszintes irányban (mezők száma).
	 */
	private int dimension_x;
	/**
	 * A világ kiterjedése függőleges irányban (mezők száma).
	 */
	private int dimension_y;
	/**
	 * Az egyetlen World példány.
	 */
	private static World instance;

	private World()
	{

	}

	public void reset()
	{
		bank = null;
		player = null;
		police = null;

		roadSegments.clear();
		sources.clear();
		steppables.clear();
		steppablesToRemove.clear();

		dimension_x = 0;
		dimension_y = 0;

		Police.reset();
		Civilian.reset();
	}

	/**
	 * Visszaadja az egyetlen World példányt.
	 */
	public static World getInstance()
	{
		if ( instance == null )
		{
			instance = new World();
		}
		return instance;
	}

	/**
	 * A játék kudarccal végződött.
	 */
	public void loseGame()
	{
		Game.getInstance().setState( GameState.Lose );
	}

	/**
	 * A játék kudarccal végződött. Külön metódus arra az esetre, ha szükség
	 * volna arra, hogy melyik objektum miatt lett vége.
	 * 
	 * @param initiator
	 *            kezdeményező objektum.
	 */
	public void loseGame( Object initiator )
	{
		loseGame();
	}

	/**
	 * Felveszi a paraméterként kapott objektumot a steppables listára.
	 * 
	 * @param steppable
	 *            IStepable interfészt megvalósító objektum.
	 */
	public void registerSteppable( ISteppable steppable )
	{
		steppables.add( steppable );
	}

	public void unregisterSteppable( ISteppable steppable )
	{
		steppablesToRemove.add( steppable );
	}

	/**
	 * Órajelütésre lépteti a benne lévő órajelre léptethető objektumokat. Ha
	 * kevés a rendőr vagy a civil, gondoskodik a pótlásukról.
	 */
	public void step()
	{
		// Ha kevés a rendőr, hozzunk létre újat.
		if ( Police.getInstanceCount() < Police.getTargetInstanceCount() )
		{
			vomitPolice();
		}
		// Ha kevés a civil, hozzunk létre újat.
		if ( Civilian.getInstanceCount() < Civilian.getTargetInstanceCount() )
		{
			vomitCivilian();
		}

		Iterator<ISteppable> itr = steppables.iterator();

		while ( itr.hasNext() )
		{
			itr.next().step();
		}

		// Amennyiben eltávolítottak eleme(ke)t az iteráció során...
		if ( steppablesToRemove.size() > 0 )
		{
			steppables.removeAll( steppablesToRemove );
			steppablesToRemove.clear();
		}
	}

	/**
	 * Rendőr "elpusztításáról" gondoskodik.
	 * 
	 * @param vehicle
	 *            Rendőr típusú jármű.
	 */
	public void visitVehicle( Police vehicle )
	{
		vehicle.kill();
	}

	/**
	 * Civil "elpusztításáról" gondoskodik.
	 * 
	 * @param vehicle
	 *            Civil típusú jármű.
	 */
	public void visitVehicle( Civilian vehicle )
	{
		vehicle.kill();
	}

	/**
	 * Rabló "elpusztításáról" gondoskodik, kezeli a játék vége eseményt.
	 * 
	 * @param vehicle
	 *            Rabló típusú jármű.
	 */
	public void visitVehicle( Robber vehicle )
	{
		vehicle.kill();
	}

	/**
	 * A játék sikerrel végződött.
	 */
	public void winGame()
	{
		Game.getInstance().setState( GameState.Win );
	}

	/**
	 * Visszatér a Bank útelemmel.
	 * 
	 * @return Bank objektum.
	 */
	public Bank getBank()
	{
		return bank;
	}

	/**
	 * Beállítja a player referenciát.
	 * 
	 * @param player
	 *            Player objektum.
	 */
	public void setPlayer( Robber player )
	{
		this.player = player;
	}

	public Robber getPlayer()
	{
		return this.player;
	}

	/**
	 * Beállítja a police referenciát.
	 * 
	 * @param police
	 *            Police objektum.
	 */
	public void setPolice( Police police )
	{
		this.police = police;
	}

	/**
	 * Visszatér a policeStation referenciával.
	 * 
	 * @return PoliceStation objektum.
	 */
	public PoliceStation getPoliceStation()
	{
		return policeStation;
	}

	/**
	 * Visszatér a pálya X irányú kiterjedésével.
	 * 
	 * @return Szélesség
	 */
	public int getDimension_x()
	{
		return dimension_x;
	}

	/**
	 * Visszatér a pálya Y irányú kiterjedésével.
	 * 
	 * @return Magasság
	 */
	public int getDimension_y()
	{
		return dimension_y;
	}

	/**
	 * Beállítja a pálya X irányú kiterjedését.
	 * 
	 * @param value
	 *            Szélesség
	 */
	public void setDimension_x( int value )
	{
		dimension_x = value;
	}

	/**
	 * Beállítja a pálya Y irányú kiterjedését.
	 * 
	 * @param value
	 *            Magasság
	 */
	public void setDimension_y( int value )
	{
		dimension_y = value;
	}

	/**
	 * Megmondja, hogy a fromRS-hez képest a toRS milyen irányba esik.
	 * 
	 * @param fromRS
	 *            Viszonyítási pontot képviselő útelem.
	 * @param toRS
	 *            Az útelem aminek a relatív irányát keressük.
	 * @return Az irány szöge.
	 */
	public float getAngleByPlacement( RoadSegment fromRS, RoadSegment toRS )
	{
		int fromIndex = roadSegments.indexOf( fromRS );
		int toIndex = roadSegments.indexOf( toRS );

		switch ( fromIndex % dimension_x - toIndex % dimension_x )
		{
		case 0: // Egymás alatt vannak.
			return (float)( Math.PI * ( fromIndex < toIndex ? 1.5d : 0.5d ) );
		case 1: // Balra esik.
			return (float)Math.PI;
		case -1: // Jobbra esik.
			return 0f;

		default: // Nem szomszédosak.
			// TODO: Itt illene kivételt dobni.
			return 0f;
		}
	}

	/**
	 * Útelemek pozícióit leíró osztály.
	 * 
	 * @author Ujhelyi
	 */
	public class Position
	{
		int x, y;

		public Position( int x, int y )
		{
			this.x = x;
			this.y = y;
		}

		public int getX()
		{
			return x;
		}

		public int getY()
		{
			return y;
		}
	}

	/**
	 * Visszatért az útelem pozíciójával.
	 * 
	 * @param rs
	 *            Keresett útelem.
	 * @return Az útelem pozíciója.
	 */
	public Position getRSPlacementInWorld( RoadSegment rs )
	{
		int index = roadSegments.indexOf( rs );
		return new Position( index % dimension_x, (int)( ( index - index % dimension_x ) / dimension_x ) );
	}

	/**
	 * Felveszi a paraméterül kapott útelemet a roadSegments listába.
	 * 
	 * @param segment
	 *            Új útelem pldány.
	 */
	public void addRoadSegment( RoadSegment segment )
	{
		roadSegments.add( segment );
	}

	/**
	 * Felveszi a paraméterül kapott Bank típusú útelemet a roadSegments
	 * listába, illetve beállítja a bank referenciát.
	 * 
	 * @param segment
	 *            Bank pldány.
	 */
	public void addRoadSegment( Bank segment )
	{
		addRoadSegment( (RoadSegment)segment );
		bank = segment;
	}

	/**
	 * Felveszi a paraméterül kapott PoliceStation típusú útelemet a
	 * roadSegments listába, illetve beállítja a policeStation referenciát.
	 * 
	 * @param segment
	 *            PoliceStation pldány.
	 */
	public void addRoadSegment( PoliceStation segment )
	{
		addRoadSegment( (RoadSegment)segment );
		policeStation = segment;
	}

	/**
	 * Felveszi a paraméterül kapott SourceRoadSegment típusú útelemet a
	 * roadSegments illetve a surces listába.
	 * 
	 * @param segment
	 *            SourceRoadSegment példány.
	 */
	public void addRoadSegment( SourceRoadSegment segment )
	{
		addRoadSegment( (RoadSegment)segment );
		sources.add( segment );
	}

	/**
	 * Létrehoz egy véletlenszerűen választott üres forrásútelemen egy rendőrt.
	 */

	public void vomitPolice()
	{
		// Véletlenszerű forráson elhelyezés
		// Logika: feljegyezzük az összes üres útforrást, azok közül véletlenül
		// választunk egyet
		List<SourceRoadSegment> potentialSources = new ArrayList<SourceRoadSegment>( sources.size() );
		for ( SourceRoadSegment srs : sources )
		{
			if ( srs.getVehicle() == null ) potentialSources.add( srs );
		}

		// Ha nincs jó hely, ebben a körben ne akarjunk semmit letenni.
		if ( potentialSources.size() == 0 ) return;

		// Új rendőr létrehozatása
		String newPoliceName = "police_" + Calendar.getInstance().getTime().toString();
		Facade.getInstance().create_police( newPoliceName );
		Police p = (Police)Facade.getInstance().getObject( newPoliceName );

		// Ráhelyezzük az újonnan létrehozott rendőrt az útra.
		int randomIndex = (int)( potentialSources.size() * Math.random() );
		potentialSources.get( randomIndex ).enter( p.getWrapper() );

	}

	public void vomitCivilian()
	{
		// Véletlenszerű forráson elhelyezés
		// Logika: feljegyezzük az összes üres útforrást, azok közül véletlenül
		// választunk egyet
		List<SourceRoadSegment> potentialSources = new ArrayList<SourceRoadSegment>( sources.size() );
		for ( SourceRoadSegment srs : sources )
		{
			if ( srs.getVehicle() == null ) potentialSources.add( srs );
		}

		// Ha nincs jó hely, ebben a körben ne akarjunk semmit letenni.
		if ( potentialSources.size() == 0 ) return;

		// Új rendőr létrehozatása
		String newCivilianName = "civil_" + Calendar.getInstance().getTime().toString();
		Facade.getInstance().create_civilian( newCivilianName );
		Civilian c = (Civilian)Facade.getInstance().getObject( newCivilianName );

		// Ráhelyezzük az újonnan létrehozott rendőrt az útra.
		int randomIndex = (int)( potentialSources.size() * Math.random() );
		potentialSources.get( randomIndex ).enter( c.getWrapper() );

	}

	/**
	 * Visszatér egy RS iterátorral.
	 * 
	 * @return Iterátor a RoadSegment listához.
	 */
	public Iterator<RoadSegment> getRSIterator()
	{
		return roadSegments.iterator();
	}
}
